package com.wosai.register.repository.jdbc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import com.wosai.register.model.OrderItem;
import com.wosai.register.repository.OrderItemRepository;
import com.wosai.register.util.ExceptionUtil;
import com.wosai.register.util.StringUtil;

@Repository
public class JdbcOrderItemRepository implements OrderItemRepository {
    private final static Log logger = LogFactory.getLog(JdbcOrderItemRepository.class);

    private NamedParameterJdbcTemplate npjt;
    private SimpleJdbcInsert sji;

    private final static String SELECT_BY_ORDER_ID = "select * from ORDER_ITEM where merchant_id = :merchantId and branch_id=:branchId and terminal_id = :terminalId and order_id=:orderId";
    private final static String SELECT_BY_TERMINAL = "select * from ORDER_ITEM where merchant_id = :merchantId and branch_id=:branchId and terminal_id = :terminalId limit :offset,:size";

    private final static String DELETE_BY_ORDER = "delete from ORDER_ITEM where merchant_id = :merchantId and branch_id=:branchId and terminal_id = :terminalId and order_id = :orderId";
    private final static String DELETE_BY_TERMINAL = "delete from ORDER_ITEM where merchant_id = :merchantId and branch_id=:branchId and terminal_id = :terminalId";
    
    private final static String UPDATE = "update ORDER_ITEM set subtotal=:subtotal, price=:price, product_id=:productId, product_name=:productName, type=:type where merchant_id=:merchantId and branch_id = :branchId and terminal_id=:terminalId and order_id=:orderId and id=:id";
    
    
    @Autowired
    public JdbcOrderItemRepository(DataSource dataSource) {
        this.sji = new SimpleJdbcInsert(dataSource)
                            .withTableName("ORDER_ITEM");

        this.npjt = new NamedParameterJdbcTemplate(dataSource);
    }

    @Override
    public List<OrderItem> findByOrderId(String merchantId, String branchId, String terminalId, String orderId) {
        
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        params.put("orderId", orderId);
        
        return this.npjt.query(SELECT_BY_ORDER_ID, params, ParameterizedBeanPropertyRowMapper.newInstance(OrderItem.class));
    }

    @Override
    public List<OrderItem> findByTerminal(String merchantId, String branchId, String terminalId, int offset, int size)
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        params.put("offset", offset);
        params.put("size", size);
        
        return this.npjt.query(SELECT_BY_TERMINAL, params, ParameterizedBeanPropertyRowMapper.newInstance(OrderItem.class));
    }


    @Override
    public List<String> insertBatch(List<OrderItem> orderItems) {
        SqlParameterSource[] batch = new SqlParameterSource[orderItems.size()];
        for(int i=0; i<batch.length; ++i)
            batch[i] = new BeanPropertySqlParameterSource(orderItems.get(i));

        ArrayList<String> failed = new ArrayList<String>();
        try{
            int[] rows = this.sji.executeBatch(batch);
            logger.info("batch inserted " + StringUtil.intArrayToString(rows));
        }catch(DataAccessException dae){
            List<Integer> failedIndices = ExceptionUtil.extractFailedStatements(dae);
            for(int i: failedIndices){
                failed.add(orderItems.get(i).getId());
            }
            logger.warn("error in batch insert " + failedIndices);
            logger.warn("detailed error info " + ExceptionUtils.getStackTrace(dae));
        }
        return failed;
    }

    @Override
    public List<String> updateBatch(List<OrderItem> orderItems) {
        SqlParameterSource[] batch = new SqlParameterSource[orderItems.size()];
        for(int i=0; i<batch.length; ++i)
            batch[i] = new BeanPropertySqlParameterSource(orderItems.get(i));

        ArrayList<String> failed = new ArrayList<String>();
        try{
            int[] rows = this.npjt.batchUpdate(UPDATE, batch);
            logger.info("batch updated " + StringUtil.intArrayToString(rows));
        }catch(DataAccessException dae){
            List<Integer> failedIndices = ExceptionUtil.extractFailedStatements(dae);
            for(int i: failedIndices){
                failed.add(orderItems.get(i).getId());
            }
            logger.warn("error in batch update " + failedIndices);
            logger.warn("detailed error info " + ExceptionUtils.getStackTrace(dae));
        }
        
        return failed;
    }

    @Override
    public void deleteByOrder(String merchantId, String branchId, String terminalId, String orderId) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        params.put("orderId", orderId);
        
        int rows = this.npjt.update(DELETE_BY_ORDER, params);
        logger.info("deleted " + rows);
    }

    @Override
    public void deleteByTerminal(String merchantId, String branchId, String terminalId) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        int rows = this.npjt.update(DELETE_BY_TERMINAL, params);
        logger.info("deleted " + rows);
    }

}
